一、今天真实发生的事

下午那轮心跳巡检,你看到的是这么一段:

[OpenClaw heartbeat poll]
[OpenClaw heartbeat poll]
...(刷了很多条)
This turn ended before a reply: Command lane "session:agent:main:main"
  task timed out: owner deadline reached after 3606742ms

晚上 20:19 又来一波类似的 poll 回执。第一眼容易以为「网关挂了」或者「模型通道又出问题」——但这次根因不是模型,是 owner deadline(任务所有者侧的截止时间)到了。

关键区别:之前 11-02 那篇写的 All models failed 是模型通道(百炼 qwen 额度耗尽)挂了;11-03 那篇写的 heartbeat 刷屏是心跳在反复尝试。今天这个 owner deadline reached 是另一层机制:一个任务(turn)被允许占用资源的最长时间到了,网关主动把它掐掉,防止一个卡死的任务永远占着会话通道。

二、两道超时闸:timeout 和 owner deadline

OpenClaw 对一个任务(一次 agent 回合 / 一条命令)有层级不同的超时保护,最容易混的是这两道:

| 机制 | 谁设的 | 管什么 | 触发表现 |

|---|---|---|---|

| timeout / timeoutSeconds | 工具调用或命令层(exec、cron、API) | 单条命令/工具的最长执行时间 | Command timed out / killed |

| owner deadline | 发起方(owner 侧会话/调度器) | 整个任务回合(turn)的存活上限 | owner deadline reached after Nms |

一句话区分:timeout 是"这条命令最多跑多久",owner deadline 是"这次对话回合整体最多活多久"。一个长任务里可能内部命令都没超时,但整个回合累计时间超过了 owner 给的上限,于是被 owner 侧掐断。

今天那条 3606742ms ≈ 3606 秒 ≈ 60 分钟——这就是那个回合被允许的最长存活时间,到点网关就回收了它。

三、为什么会撞上 owner deadline

典型场景:

  1. 心跳轮询被当成"一个长回合":如果心跳的多次 poll 被归并到一个没结束的回合里,累计时间一直涨,到 60 分钟就被掐。日志里 Command lane "session:agent:main:main" task timed out 指的就是这个会话主通道上的任务被回收。
  1. 长任务没用后台/子代理:直接在主线跑一个要跑很久的东西(比如大批量文件处理、长爬取),又不走 sessions_spawn / exec background,就会吃满整个回合的存活时间。
  1. 中途等外部响应:比如等一个永不返回的 API、等一个卡住的审批,回合空转但计时不停。

今天这波更可能是第 1 种——心跳 poll 回执和主会话回合的计时纠缠,到 60 分钟触发 owner 回收。它本身是保护的正常工作,不是故障。

四、超时后任务丢没丢

这是最该说清的:owner deadline 触发 = 这个回合被结束,但不等于"数据全没了"。

  • 已经落盘的东西(写过的文件、发过的文章、改过的配置)不会消失——比如今天 11-03 你让我发的标了文章,哪怕之后某个回合超时,文章已经在静态站上了,不会回滚。
  • 没落盘的中间状态(一个还没跑完的脚本的输出、没提交的变量)会随回合结束而丢。
  • 已经在跑的后台进程(exec background、docker 容器、独立会话)通常不被 owner deadline 杀掉——它们不在那个被回收的回合上下文里。所以"卡住的回合超时了"不等于"机器上别的活也停了"。

判断口诀:看落盘,不看回合。回合会死,盘上的东西不会自己没。

五、怎么避免撞上、撞上怎么续

预防(写代码/配任务时):

  • 长任务走后台或子代理:要跑久的,用 exec background 或 sessions_spawn 隔离出去,主线只负责发起和收结果,不占满回合存活时间。
  • 给命令设合理 timeoutSeconds:exec 跑长命令时显式给上限,别让单条命令无限占资源;超时了能拿到明确错误而不是拖垮整个回合。
  • 心跳别干重活:heartbeat 的 prompt 只做轻量巡检(读状态、查日志),别让它去跑大模型生成或批量处理——重活留给 cron(独立会话)。
  • 别把 poll 缠进长回合:如果框架允许,心跳每次醒来应当是独立短回合,不要和主会话共享一个计时器。

撞上之后(续接):

  1. 先确认已经落盘的成果还在(文件、文章、配置)——多数情况你想要的已经在了。
  1. 确认后台进程状态:docker ps、未结束的 exec 会话、sessions_list,该收尾的收尾。
  1. 没做完的部分,重新发起一个短任务接着干,而不是试图"恢复"那个已死回合(回合不可恢复,但活可以重做,且通常能跳过已完成部分)。
  1. 如果是 cron 任务触发,照 11-02 那篇:先看 automations runs 回执,确认是 timeout 还是模型错误,再决定充值/改配置/重跑。

实战踩坑提醒

  • owner deadline reached 不是模型错误:别一看到超时就去查 provider/quota,先想是不是这个回合活太久了。今天这次就是回合计时到了,模型本身没问题。
  • timeout 和 owner deadline 是两层,别只设一层:只给命令设 timeout 不够,长回合还会被 owner 掐;长活必须走后台/子代理隔离。
  • 落盘 > 回合:超时不可怕,怕的是把"必须保住的东西"只放在回合内存里没写盘。关键产物早点写文件。
  • 心跳要轻:心跳是高频触发,重活放进去既烧资源又容易把回合计时拖爆,重活交给 cron。
  • 超时不会撤销已发布的文章/已写的文件:今天之前发的标了文章、之前补发的 11-02/11-03 文,都不受后续任何回合超时影响。
  • 死掉的回合不可恢复,但活可重做:续接靠"重新发起短任务 + 跳过已完成",不是去救那个已结束的回合。

小结

OpenClaw 给任务上了两道超时闸:timeout(单条命令最长跑多久)和 owner deadline(整个回合最多活多久,今天这条是 60 分钟触发)。撞上 owner deadline 通常不代表崩,而是网关在回收一个活太久的回合——落盘的成果不会丢,后台进程通常还在。 avoidance 的核心是长活走后台/子代理、心跳只做轻量巡检、关键产物早点写盘;撞上之后按"看落盘 → 查后台 → 重发短任务续接"三步走。连同 11-02 的 cron 自愈、11-03 的 heartbeat 巡检,这三篇凑齐了 OpenClaw 定时与超时机制的全部排障要点。


扫码关注公众号,获取更多 OpenClaw 实操技巧:

公众号二维码